﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Xml.Serialization;

namespace d39
{
    [Serializable]
    public class Player
    {
        public Player()
        {
            Size = 1;
            Shape = new CircleShape();
            Color = Color.FromArgb(0, 0, 0);
            Positions = new List<PositionMove>();
            Initiative = new List<int>();
        }

        /// <summary>
        /// 0 = Tiny/Diminutive/Fine etc
        /// 1 = Small/Medium
        /// 2 = Large
        /// 3 = Huge
        /// 4 = Gargantuan
        /// 6+ = Colossal
        /// </summary>
        [XmlAttribute("size")]
        public int Size { get; set; }

        [XmlIgnore]
        public IShape Shape { get { return Shapes.FromShapeChar(ShapeChar); } set { ShapeChar = value.ShapeChar; } }

        [XmlAttribute("shape")]
        public char ShapeChar { get; set; }

        [XmlIgnore]
        public Color Color { get; set; }

        [XmlAttribute("color")]
        public string ColorString
        {
            get { return ColorTranslator.ToHtml(Color); }
            set { Color = ColorTranslator.FromHtml(value); }
        }

        [XmlAttribute("fixed")]
        public bool IsFixed { get; set; }

        [XmlAttribute("multi")]
        public bool IsMulti { get; set; }

        [XmlIgnore]
        public bool IsMovable { get { return !IsFixed; } set { IsFixed = !value; } }

        [XmlIgnore]
        public bool IsAllInvisible { get { return Positions.Any() && Positions.All(p => p.IsInvisible); } }

        [XmlIgnore]
        public bool IsAllInactive { get { return Positions.Any() && Positions.All(p => p.IsInactive); } }

        [XmlIgnore]
        public bool IsUnknown { get { return Positions.Any() && Positions.All(p => p.IsUnknown); } }

        [XmlIgnore]
        public string SizeName
        {
            get { return Sizes.AllSizes[Size]; }
        }

        [XmlElement("at")]
        public List<PositionMove> Positions { get; set; }

        [XmlIgnore]
        public IEnumerable<PositionMove> KnownPositions { get { return Positions.Where(p => !p.IsUnknown); } }

        public IEnumerable<PositionMove> GetRevealedPositions(Grid grid, UrlTweaks tweaks = null)
        {
            return GetRevealedPositions((tweaks ?? UrlTweaks.Default).RevealUnknown ?? grid.RevealUnknown);
        }

        public IEnumerable<PositionMove> GetRevealedPositions(bool revealUnknown)
        {
            return revealUnknown ? Positions : KnownPositions;
        }

        public IEnumerable<PositionMove> GetRevealedPositionsActiveFirst(Grid grid, UrlTweaks tweaks = null)
        {
            var revealedPositions = GetRevealedPositions(grid, tweaks);
            return revealedPositions.Where(pm => !pm.IsInactive).Concat(revealedPositions.Where(pm => pm.IsInactive));
        }

        public string GetRevealedPositionsString(Grid grid, UrlTweaks tweaks = null)
        {
            var revealedPositions = GetRevealedPositions(grid, tweaks);
            var activePositions = revealedPositions.Where(pm => !pm.IsInactive);
            var inactivePositions = revealedPositions.Where(pm => pm.IsInactive);
            var result = string.Join(", ", from pm in activePositions select pm.ToHumanReadablePositionString());
            if (inactivePositions.Any()) result += " (" + string.Join(", ", from pm in inactivePositions select pm.ToHumanReadablePositionString()) + ")";
            return result;
        }

        [XmlIgnore]
        public int? InitiativeMod { get; set; }

        [XmlAttribute("init-mod")]
        public string InitiativeModString
        {
            get { return "" + InitiativeMod; }
            set { InitiativeMod = string.IsNullOrEmpty(value) ? (int?)null : int.Parse(value); }
        }

        [XmlElement("init")]
        public List<int> Initiative { get; set; }

        [XmlAttribute("name")]
        public string Name { get; set; }

        [XmlAttribute("description")]
        public string Description { get; set; }

        [XmlIgnore]
        public string NameAndPositions { get { return Name + InitiativeString + (Positions.Count == 1 ? ": " + Positions[0].ToFullHumanReadablePositionString() : ""); } }

        [XmlIgnore]
        public string InitiativeString
        {
            get
            {
                if (InitiativeMod == null) return null;

                return string.Format(" (Init: d20{0:+0;-0}{1})", InitiativeMod, Initiative.Any() ? "=" + string.Join("/", Initiative) : "");
            }
        }

        public IEnumerable<string> GetRowsOfPositions(Grid grid, UrlTweaks tweaks = null)
        {
            var positionsActiveFirst = GetRevealedPositionsActiveFirst(grid, tweaks);
            var count = positionsActiveFirst.Count();

            if (count <= 1) yield break;
            var inactive = false;
            for (int i = 0; i < Positions.Count; i += 10)
            {
                var positionsRow = positionsActiveFirst.Skip(i).Take(10);
                var result = "";
                foreach (var pm in positionsRow)
                {
                    if (result != "") result += ", ";
                    if (!inactive && pm.IsInactive)
                    {
                        result += "(";
                        inactive = true;
                    }
                    result += pm.ToHumanReadablePositionString();
                }
                if (i + 10 < count) result += ",";
                else if (inactive) result += ")";
                yield return result;
            }
        }

        [XmlIgnore]
        public int SizeInSquares { get { return Math.Max(Size, 1); } }

        public int GetImageSize(int pixelSize)
        {
            return SizeInSquares * pixelSize + 1;
        }

        public void Draw(Graphics graphics, Grid grid, Coordinate position, bool filled, bool semiTransparent, IEnumerable<Vector> moves)
        {
            var pixelSize = Size == 0 ? ((float)grid.PixelSize / 3) : grid.PixelSize * Size;
            var color = Color;
            if (semiTransparent) color = Color.FromArgb(128, Color.R, Color.G, Color.B);
            Shape.Draw(graphics, color, filled, coordinateToCenterPixel(grid, pixelSize, position), pixelSize);
            var pos = position;
            foreach (var m in moves.Reverse())
            {
                var lastPos = pos;
                pos -= m;
                Shape.Draw(graphics, color, filled, coordinateToCenterPixel(grid, pixelSize, pos), pixelSize / 2.0f);
                using (var pen = new Pen(color))
                {
                    graphics.DrawLine(pen, coordinateToCenterPixel(grid, pixelSize, pos), coordinateToCenterPixel(grid, pixelSize, lastPos));
                }
            }
        }
        private static PointF coordinateToCenterPixel(Grid grid, float pixelSize, Coordinate pos)
        {
            return new PointF(grid.HalfCoordinateToPixel(pos.X * 2) + pixelSize / 2.0f, grid.HalfCoordinateToPixel(pos.Y * 2) + pixelSize / 2.0f);
        }

        // FIXME: this should also have an UrlTweaks, because of pixelSizeOverride
        [XmlIgnore]
        public string ImageUrl
        {
            get
            {
                var px = HttpContext.Current.Request.QueryString["px"];
                var pxStr = (px == null || px == "10") ? "" : "px=" + px + "&";
                var url = HttpContext.Current.Request.Url;
                var tweaks = new UrlTweaks() { IncludeDescriptions = false, IncludePlayerPositions = false };
                return url.Scheme + "://" + url.Host + portStr(url) + "/PlayerImage.aspx?" + pxStr + "&p=" + HttpUtility.UrlEncode(ToString(tweaks));
            }
        }
        private static string portStr(Uri url)
        {
            return url.IsDefaultPort ? "" : ":" + url.Port;
        }

        /// <summary>
        /// String format is as follows: FMImiiSs000000PM.PM-D
        /// F = "F" if fixed, missing otherwise
        /// M = "M" if multiples allowed, missing otherwise
        /// Imii = initiative: "I" if applicable, followed by modifier, followed by actual initiative rolls
        /// S = Size
        /// s = Shape (see IShape.cs)
        /// 000000 = Color, html format
        /// P = Position, see Coordinate.cs; optional
        /// M = Move, see Moves.cs; optional
        /// D = Description (omitted if not includeDescription)
        /// </summary>
        /// <param name="includeDescription"></param>
        /// <returns></returns>
        public string ToString(UrlTweaks tweaks)
        {
            var includeDescription = tweaks.IncludeDescriptions;
            if (string.IsNullOrEmpty(Description) && string.IsNullOrEmpty(Name)) includeDescription = false;
            var start = (IsFixed ? "F" : "") + (IsMulti || Positions.Count > 1 ? "M" : "");
            if (InitiativeMod != null)
            {
                start += 'I' + Base26.IntToString((int)InitiativeMod) + string.Concat(from i in Initiative select Base26.IntToString(i));
            }
            start += Size.ToString() + ShapeChar + ColorTranslator.ToHtml(Color).Substring(1);
            if (tweaks.IncludePlayerPositions) start += string.Join(".", from pos in Positions where tweaks.IncludeUnknown || !pos.IsUnknown select pos.ToString(tweaks));
            return includeDescription ? start + '-' + Name + (string.IsNullOrEmpty(Description) ? "" : ("_" + Description)) : start;
        }
        public static Player FromString(string str, DnDVersion ver)
        {
            var player = new Player();
            int pos = 0;
            if (str[pos] == 'F')
            {
                player.IsFixed = true;
                pos++;
            }
            if (str[pos] == 'M')
            {
                player.IsMulti = true;
                pos++;
            }
            if (str[pos] == 'I')
            {
                pos++;
                player.InitiativeMod = Base26.ParseInt(str, pos, out pos);
                while (!(str[pos] >= '0' && str[pos] <= '9')) player.Initiative.Add(Base26.ParseInt(str, pos, out pos));
            }
            player.Size = int.Parse(str[pos++].ToString());
            player.Shape = Shapes.FromShapeChar(str[pos++]);
            player.Color = ColorTranslator.FromHtml("#" + str.Substring(pos, 6));
            pos += 6;
            var dashPos = str.IndexOf('-', pos);
            if (dashPos >= 0)
            {
                player.Name = str.Substring(dashPos + 1);
                int underPos = player.Name.IndexOf('_');
                if (underPos >= 0)
                {
                    player.Description = player.Name.Substring(underPos + 1);
                    player.Name = player.Name.Substring(0, underPos);
                }
                player.Positions = PositionMove.ParseMulti(player, ver, str.Substring(pos, dashPos - pos));
            }
            else
            {
                player.Positions = PositionMove.ParseMulti(player, ver, str.Substring(pos));
            }
            if (player.Positions.Count > 1) player.IsMulti = true;
            return player;
        }
    }
}